/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// TGUI - Texus' Graphical User Interface
// Copyright (C) 2012-2025 Bruno Van de Velde (vdv_b@tgui.eu)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
//    you must not claim that you wrote the original software.
//    If you use this software in a product, an acknowledgment
//    in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
//    and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef TGUI_TEXT_AREA_HPP
#define TGUI_TEXT_AREA_HPP

#include <TGUI/Widgets/Scrollbar.hpp>
#include <TGUI/Renderers/TextAreaRenderer.hpp>
#include <TGUI/Text.hpp>

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace tgui
{
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// @brief Text area widget
    ///
    /// A text area is a multi-line input box which supports word-wrap and a vertical scrollbar.
    /// If you are looking for a single line input field then check out the EditBox class.
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    class TGUI_API TextArea : public Widget, public DualScrollbarChildInterface
    {
    public:

        using Ptr = std::shared_ptr<TextArea>; //!< Shared widget pointer
        using ConstPtr = std::shared_ptr<const TextArea>; //!< Shared constant widget pointer

        static constexpr const char StaticWidgetType[] = "TextArea"; //!< Type name of the widget

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /// @brief Constructor
        /// @param typeName     Type of the widget
        /// @param initRenderer Should the renderer be initialized? Should be true unless a derived class initializes it.
        /// @see create
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TextArea(const char* typeName = StaticWidgetType, bool initRenderer = true);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Creates a new text area widget
        ///
        /// @return The new text area
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD static TextArea::Ptr create();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Makes a copy of another text area
        ///
        /// @param textArea  The other text area
        ///
        /// @return The new text area
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD static TextArea::Ptr copy(const TextArea::ConstPtr& textArea);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the renderer, which gives access to functions that determine how the widget is displayed
        /// @return Temporary pointer to the renderer that may be shared with other widgets using the same renderer
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD TextAreaRenderer* getSharedRenderer() override;
        TGUI_NODISCARD const TextAreaRenderer* getSharedRenderer() const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the renderer, which gives access to functions that determine how the widget is displayed
        /// @return Temporary pointer to the renderer
        /// @warning After calling this function, the widget has its own copy of the renderer and it will no longer be shared.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD TextAreaRenderer* getRenderer() override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the size of the text area
        ///
        /// This size does not include the borders.
        ///
        /// @param size  The new size of the text area
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setSize(const Layout2d& size) override;
        using Widget::setSize;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the text of the text area
        ///
        /// @param text  New text
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setText(String text);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Appends some text to the text that was already in the text area
        ///
        /// @param text  Text that will be added to the text that is already in the text area
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void addText(String text);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the text of the text area
        ///
        /// @return  The text that is currently inside the text area
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD String getText() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the default text of the text area. This is the text drawn when the text area is empty
        ///
        /// @param text  The new default text
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setDefaultText(const String& text);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the default text of the text area. This is the text drawn when the text area is empty
        ///
        /// @return The default text of the text area
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD const String& getDefaultText() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes which part of the text is selected
        ///
        /// @param selectionStartIndex Amount of characters before the start of the selection
        /// @param selectionEndIndex   Amount of characters before the end of the selection
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setSelectedText(std::size_t selectionStartIndex, std::size_t selectionEndIndex);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the text that you currently have selected
        ///
        /// @return The selected text of the text area
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD String getSelectedText() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the index where the selection starts
        ///
        /// When no text is selected, this function returns the same getSelectionEnd.
        ///
        /// Note that the start of the selection may be behind the end of the selection when the user selected the text from
        /// right to left or from bottom to top.
        ///
        /// @return Amount of characters before the start of the selection
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getSelectionStart() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the index where the selection ends
        ///
        /// The selection end always equals the caret position.
        ///
        /// Note that the start of the selection may be behind the end of the selection when the user selected the text from
        /// right to left or from bottom to top.
        ///
        /// @return Amount of characters before the end of the selection
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getSelectionEnd() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the maximum character limit
        ///
        /// @param maxChars  The new character limit.
        ///                  Set it to 0 to disable the limit
        ///
        /// This character limit is disabled by default.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setMaximumCharacters(std::size_t maxChars = 0);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the maximum character limit
        ///
        /// @return The character limit
        ///         The function will return 0 when there is no limit.
        ///
        /// There is no character limit by default.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getMaximumCharacters() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the string that is inserted when the Tab key is pressed
        ///
        /// @param tabText  The string to insert when Tab is pressed.
        ///
        /// Defaults to "\t".
        ///
        /// @warning Any cartridge return characters given in this string (\r) will be removed!
        ///
        /// @warning Pressing the Tab key will not affect the TextArea unless BackendGui::setTabKeyUsageEnabled(false) is called.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setTabString(String tabText);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the string that is inserted when the Tab key is pressed
        ///
        /// @return The string that's inserted when Tab is pressed.
        ///
        /// Defaults to "\t".
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD String getTabString() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Sets the blinking caret to after a specific character
        ///
        /// @param charactersBeforeCaret  The new position
        ///
        /// This function will set both the selection start and selection end to the requested value.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setCaretPosition(std::size_t charactersBeforeCaret);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns after which character the blinking cursor is currently located
        ///
        /// @return Characters before the caret
        ///
        /// This function is an alias for getSelectionEnd.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getCaretPosition() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns which line the blinking cursor is currently located on
        ///
        /// @return The line the caret is currently located on
        ///
        /// This function will take word-wrap into account. So, if a caret is on a line that is
        /// currently wrapping, the caret will still register as being on the same line even if
        /// that line spans many lines in the TextArea.
        ///
        /// If the value is at the top, the value will be 1.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getCaretLine() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns which column the blinking cursor is currently located on
        ///
        /// @return Characters before the caret on the caret's current line
        ///
        /// This function will take word-wrap into account. So, if a caret is on a line that is
        /// currently wrapping, the caret will still register as being on the same line even if
        /// that line spans many lines in the TextArea.
        ///
        /// If the caret is at the beginning of a line, the value will be 1.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getCaretColumn() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Makes the text area read-only or make it writable again
        ///
        /// @param readOnly  Should the text area be read-only?
        ///
        /// When the text area is read-only, you can no longer delete characters and type text.
        /// Selecting text, copying text and even calling the setText function will still work.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setReadOnly(bool readOnly = true);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Checks if the text area read-only or writable
        ///
        /// @return Is the text area read-only?
        ///
        /// When the text area is read-only, you can no longer delete characters and type text.
        /// Selecting text, copying text and even calling the setText function will still work.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD bool isReadOnly() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes when the vertical scrollbar should be displayed
        /// @param policy  The policy for displaying the vertical scrollbar
        ///
        /// The default policy is Automatic, which means it only shows when the text doesn't fit inside the TextArea.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getVerticalScrollbar->setPolicy(policy) instead") void setVerticalScrollbarPolicy(Scrollbar::Policy policy);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns when the vertical scrollbar should be displayed
        /// @return The policy for displaying the vertical scrollbar
        ///
        /// The default policy is Automatic, which means it only shows when the text doesn't fit inside the TextArea.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getVerticalScrollbar->getPolicy() instead") TGUI_NODISCARD Scrollbar::Policy getVerticalScrollbarPolicy() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes when the horizontal scrollbar should be displayed
        /// @param policy  The policy for displaying the horizontal scrollbar
        ///
        /// The default policy is Never, which means word-wrap will be used to keep the text within the TextArea.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getHorizontalScrollbar->setPolicy(policy) instead") void setHorizontalScrollbarPolicy(Scrollbar::Policy policy);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns when the horizontal scrollbar should be displayed
        /// @return The policy for displaying the horizontal scrollbar
        ///
        /// The default policy is Never, which means word-wrap will be used to keep the text within the TextArea.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getHorizontalScrollbar->getPolicy() instead") TGUI_NODISCARD Scrollbar::Policy getHorizontalScrollbarPolicy() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the amount of lines that the text occupies in the TextArea
        ///
        /// @return Lines of text
        ///
        /// Note that this is the amount of lines after word-wrap is applied.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getLinesCount() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Focus or unfocus the widget
        /// @param focused  Is the widget focused?
        ///
        /// When a widget is focused, the previously focused widget will be unfocused.
        ///
        /// @warning This function only works properly when the widget was already added to its parent (e.g. the Gui).
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setFocused(bool focused) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes whether an optimization is made that only works when using a monospaced font
        ///
        /// @param enable  Whether the optimization should be enabled
        ///
        /// Rearranging text when a horizontal scrollbar is enabled can be almost as expensive as the word-wrap which is
        /// performed when there is no horizontal scrollbar. When using a monospaced font there is no need for such expensive
        /// calculation. Call this function when using a monospaced font to make changing the text faster.
        ///
        /// This function has no effect when the horizontal scrollbar is disabled (default).
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void enableMonospacedFontOptimization(bool enable = true);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the thumb position of the vertical scrollbar
        ///
        /// @param value  New value of the vertical scrollbar
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getVerticalScrollbar->setValue(value) instead") void setVerticalScrollbarValue(unsigned int value);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the thumb position of the vertical scrollbar
        ///
        /// @return Value of the vertical scrollbar
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getVerticalScrollbar->getValue() instead") TGUI_NODISCARD unsigned int getVerticalScrollbarValue() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the maximum thumb position of the vertical scrollbar
        ///
        /// @return Maximum value of the vertical scrollbar
        ///
        /// @since TGUI 1.4
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getVerticalScrollbar->getMaxValue() instead") TGUI_NODISCARD unsigned int getVerticalScrollbarMaxValue() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the thumb position of the horizontal scrollbar
        ///
        /// @param value  New value of the horizontal scrollbar
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getHorizontalScrollbar->setValue(value) instead") void setHorizontalScrollbarValue(unsigned int value);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the thumb position of the horizontal scrollbar
        ///
        /// @return Value of the horizontal scrollbar
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getHorizontalScrollbar->getValue() instead") TGUI_NODISCARD unsigned int getHorizontalScrollbarValue() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the maximum thumb position of the horizontal scrollbar
        ///
        /// @return Maximum value of the horizontal scrollbar
        ///
        /// @since TGUI 1.4
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_DEPRECATED("Use getHorizontalScrollbar->getMaxValue() instead") TGUI_NODISCARD unsigned int getHorizontalScrollbarMaxValue() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns whether the mouse position (which is relative to the parent widget) lies on top of the widget
        ///
        /// @return Is the mouse on top of the widget?
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD bool isMouseOnWidget(Vector2f pos) const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool leftMousePressed(Vector2f pos) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void leftMouseReleased(Vector2f pos) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void mouseMoved(Vector2f pos) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void keyPressed(const Event::KeyEvent& event) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Called by the parent of the widget to check if keyPressed would process the event
        ///
        /// @param event  Key event that took place
        ///
        /// @return True if the event would be handled by the widget, false if the key event doesn't affect the widget
        ///
        /// @since TGUI 1.1
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool canHandleKeyPress(const Event::KeyEvent& event) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void textEntered(char32_t key) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool scrolled(float delta, Vector2f pos, bool touch) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void mouseNoLongerOnWidget() override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void leftMouseButtonNoLongerDown() override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    protected:

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // This function will search after which character the caret should be placed. It will not change the caret position.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD Vector2<std::size_t> findCaretPosition(Vector2f position) const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Gets the index of either m_selStart or m_selEnd
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getIndexOfSelectionPos(Vector2<std::size_t> selectionPos) const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Removes the selected characters. This function is called when pressing backspace, delete or a letter while there were
        // some characters selected.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void deleteSelectedCharacters();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Inserts text at the current caret position.
        // If the given string exceeds the maximum character limit,
        // excess characters from the right side of the string will not be inserted.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void insertTextAtCaretPosition(String text);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Rearrange the text inside the text area (by using word wrap).
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void rearrangeText(bool keepSelection, const bool emitCaretChangedPosition = true);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Updates the physical size of the scrollbars, as well as the viewport size.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void updateScrollbars();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // This function will split the text into five pieces so that the text can be easily drawn.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void updateSelectionTexts();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Ctrl+Backspace" key press (or equivalent on macOS)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void deleteWordLeft();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Ctrl+Delete" key press (or equivalent on macOS)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void deleteWordRight();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Backspace" key press (when Ctrl isn't being pressed, otherwise deleteWordLeft is called)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void backspaceKeyPressed();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Delete" key press (when Ctrl isn't being pressed, otherwise deleteWordRight is called)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void deleteKeyPressed();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Ctrl+C" key press (or equivalent on macOS)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void copySelectedTextToClipboard();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Ctrl+X" key press (or equivalent on macOS)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void cutSelectedTextToClipboard();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Ctrl+V" key press (or equivalent on macOS)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void pasteTextFromClipboard();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Ctrl+A" key press (or equivalent on macOS)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void selectAllText();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "PageUp" key press
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void moveCaretPageUp();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "PageDown" key press
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void moveCaretPageDown();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "ArrowLeft" key press
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void moveCaretLeft(bool shiftPressed);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "ArrowRight" key press
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void moveCaretRight(bool shiftPressed);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Ctrl+ArrowLeft" key press (or equivalent on macOS)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void moveCaretWordBegin();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Handles "Ctrl+ArrowRight" key press (or equivalent on macOS)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void moveCaretWordEnd();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Draw the widget to a render target
        ///
        /// @param target Render target to draw to
        /// @param states Current render states
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void draw(BackendRenderTarget& target, RenderStates states) const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    private:

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Implementation of setCaretPosition() that either updates or retains the m_selEnd value.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setCaretPositionImpl(std::size_t charactersBeforeCaret, bool selEndNeedUpdate, bool emitCaretChangedPosition);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    protected:

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Returns the size without the borders
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD Vector2f getInnerSize() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // This function is called every frame with the time passed since the last frame.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool updateTime(Duration elapsedTime) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Recalculates the positions of the contents of the text area.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void recalculatePositions();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Recalculates which lines are currently visible.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void recalculateVisibleLines();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Retrieves a signal based on its name
        ///
        /// @param signalName  Name of the signal
        ///
        /// @return Signal that corresponds to the name
        ///
        /// @throw Exception when the name does not match any signal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD Signal& getSignal(String signalName) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Function called when one of the properties of the renderer is changed
        ///
        /// @param property  Name of the property that was changed
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void rendererChanged(const String& property) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Saves the widget as a tree node in order to save it to a file
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::unique_ptr<DataIO::Node> save(SavingRenderersMap& renderers) const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Loads the widget from a tree of nodes
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void load(const std::unique_ptr<DataIO::Node>& node, const LoadingRenderersMap& renderers) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Called when the text size is changed (either by setTextSize or via the renderer)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void updateTextSize() override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Called when the policy of one of the scrollbars has been changed calling either
        ///        getVerticalScrollbar()->setPolicy(...) or getHorizontalScrollbar()->setPolicy(...)
        ///
        /// @param orientation  Vertical or Horizontal depending on which scrollbar triggered the callback
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void scrollbarPolicyChanged(Orientation orientation) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Makes a copy of the widget
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD Widget::Ptr clone() const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Updates m_selEnd with a new value and emits the onCaretPositionChange signal
        // @param newValue the value to assign to m_selEnd.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void updateSelEnd(const Vector2<std::size_t>& newValue);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    public:

        SignalString onTextChange = {"TextChanged"};             //!< The text was changed. Optional parameter: new text
        Signal onSelectionChange = {"SelectionChanged"};         //!< Selected text changed
        Signal onCaretPositionChange = {"CaretPositionChanged"}; //!< Caret position changed

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    protected:

        String m_text;
        float m_lineHeight = 24;

        // The width of the largest line
        float m_maxLineWidth = 0;

        std::vector<String> m_lines;

        // The maximum characters (0 by default, which means no limit)
        std::size_t m_maxChars = 0;

        // What is known about the visible lines?
        std::size_t m_topLine = 1;
        std::size_t m_visibleLines = 1;

        // Information about the selection
        Vector2<std::size_t> m_selStart;
        Vector2<std::size_t> m_selEnd;
        std::pair<Vector2<std::size_t>, Vector2<std::size_t>> m_lastSelection;

        // Information about the caret
        Vector2f m_caretPosition;
        bool m_caretVisible = true;

        // The text to insert when Tab is pressed
        String m_tabText = U"\t";

        Text m_textBeforeSelection;
        Text m_textSelection1;
        Text m_textSelection2;
        Text m_textAfterSelection1;
        Text m_textAfterSelection2;
        Text m_defaultText;

        std::vector<FloatRect> m_selectionRects;

        // Is there a possibility that the user is going to double click?
        bool m_possibleDoubleClick = false;

        bool m_readOnly = false;

        bool m_monospacedFontOptimizationEnabled = false;

        Sprite m_spriteBackground;

        // Cached renderer properties
        Borders m_bordersCached;
        Padding m_paddingCached;
        Color   m_borderColorCached;
        Color   m_backgroundColorCached;
        Color   m_caretColorCached;
        Color   m_selectedTextBackgroundColorCached;
        float   m_caretWidthCached = 1;
        float   m_roundedBorderRadiusCached = 0;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    };

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#endif // TGUI_TEXT_AREA_HPP
